﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CAEX_ClassModel;
using AMLEngineExtensions;
using System.Collections;

namespace AutomationMLMapper.AutomationML.Extensions
{
    /// <summary>
    /// Comparing Result Enumeration 
    /// </summary>
    [Flags]
    public enum CaexObjectComparerResult
    {
        IsEqual = 0,
        PathDiff = 1,
        InterfaceClassDiff = 2,
        AttributeDiff = 4,
        AttributeDataTypeDiff = 8,
        AttributeUnitDiff = 16,
        AttributeCountDiff = 32,
        RoleClassDiff = 64,
        InterfaceCountDiff = 128
    }
    
  
  
    /// <summary>
    /// Caex Comparer Extension Methods and Result Dictionary
    /// </summary>
    public static class CaexObjectComparer
    {
        public static string CaexObjectComparerResultString(CaexObjectComparerResult result)
        {
            StringBuilder sb = new StringBuilder();
            if (result.HasFlag (CaexObjectComparerResult.PathDiff))
                sb.AppendLine (CaexObjectComparerResult.PathDiff.ToString());
            if (result.HasFlag(CaexObjectComparerResult.InterfaceClassDiff))
                sb.AppendLine(CaexObjectComparerResult.InterfaceClassDiff.ToString());
            if (result.HasFlag(CaexObjectComparerResult.AttributeDiff))
                sb.AppendLine(CaexObjectComparerResult.AttributeDiff.ToString());
            if (result.HasFlag(CaexObjectComparerResult.AttributeDataTypeDiff))
                sb.AppendLine(CaexObjectComparerResult.AttributeDataTypeDiff.ToString());
            if (result.HasFlag(CaexObjectComparerResult.AttributeUnitDiff))
                sb.AppendLine(CaexObjectComparerResult.AttributeUnitDiff.ToString());
            if (result.HasFlag(CaexObjectComparerResult.AttributeCountDiff))
                sb.AppendLine(CaexObjectComparerResult.AttributeCountDiff.ToString());
            if (result.HasFlag(CaexObjectComparerResult.RoleClassDiff))
                sb.AppendLine(CaexObjectComparerResult.RoleClassDiff.ToString());
            if (result.HasFlag(CaexObjectComparerResult.InterfaceCountDiff))
                sb.AppendLine(CaexObjectComparerResult.InterfaceCountDiff.ToString());

            return sb.ToString();
        }

        /// <summary>
        /// Dictionary for Comparing Results
        /// </summary>
        public static Dictionary<Tuple<CAEXBasicObject,CAEXBasicObject>, CaexObjectComparerResult> CaexComparedObjects;

        /// <summary>
        /// Reset the Result - Dictionary of the Comparer
        /// </summary>
        public static void ResetComparerResult()
        {
            if (CaexComparedObjects != null)
                CaexComparedObjects.Clear();
        }

        /// <summary>
        /// Start Comparing a tuple and add it to the Dictionary 
        /// </summary>
        /// <param name="caexBasicObj"></param>
        /// <param name="compareWith"></param>
        private static Tuple<CAEXBasicObject,CAEXBasicObject> BeginCompare(this CAEXObject caexBasicObj, CAEXObject compareWith)
        {
            if (CaexComparedObjects == null)
                CaexComparedObjects = new Dictionary<Tuple<CAEXBasicObject, CAEXBasicObject>, CaexObjectComparerResult>();

            var comparedObjects = new Tuple<CAEXBasicObject,CAEXBasicObject>(caexBasicObj,compareWith);
            
            if (!CaexComparedObjects.ContainsKey (comparedObjects ) )        
                CaexComparedObjects.Add ( comparedObjects, CaexObjectComparerResult.IsEqual);

            return comparedObjects;
        }

        /// <summary>
        /// Get all AttributeType - Tuples and the Compare-Result from the Compare Result-Dictionary with an AttributeDiff-Flag
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Tuple<AttributeType, AttributeType, CaexObjectComparerResult>> DifferentAttributeTypes()
        {
            return CaexComparedObjects.Keys.Where(c => (c is AttributeType) && CaexComparedObjects[c].HasFlag(CaexObjectComparerResult.AttributeDiff)).Select
                (s => new Tuple<AttributeType, AttributeType, CaexObjectComparerResult>((AttributeType)s.Item1, (AttributeType)s.Item2, CaexComparedObjects[s]));
        }

        /// <summary>
        /// Get all InterfaceClassType - Tuples and the Compare-Result from the Compare Result-Dictionary with an InterfaceClassDiff-Flag
        /// </summary>
        /// <returns></returns>        
        public static IEnumerable<Tuple<InterfaceClassType, InterfaceClassType, CaexObjectComparerResult>> DifferentInterfaceClassTypes()
        {
            return CaexComparedObjects.Keys.Where(c => (c is InterfaceClassType) && CaexComparedObjects[c].HasFlag(CaexObjectComparerResult.InterfaceClassDiff)).Select
                (s => new Tuple<InterfaceClassType, InterfaceClassType, CaexObjectComparerResult>((InterfaceClassType)s.Item1, (InterfaceClassType)s.Item2, CaexComparedObjects[s]));
        }

        /// <summary>
        /// Get all RoleClassType - Tuples and the Compare-Result from the Compare Result-Dictionary with a RoleClassDiff-Flag
        /// </summary>
        /// <returns></returns>        
        public static IEnumerable<Tuple<RoleClassType, RoleClassType, CaexObjectComparerResult>> DifferentRoleClassTypes()
        {
            return CaexComparedObjects.Keys.Where(c => (c.Item1 is RoleClassType) && CaexComparedObjects[c].HasFlag(CaexObjectComparerResult.RoleClassDiff)).Select
                (s => new Tuple<RoleClassType, RoleClassType, CaexObjectComparerResult>((RoleClassType)s.Item1, (RoleClassType)s.Item2, CaexComparedObjects[s]));
        }

        /// <summary>
        /// Compare the CAEXPatch() of two CaexBasicObjects.  
        /// </summary>
        /// <param name="caexBasicObj"></param>
        /// <param name="compareWith"></param>
        /// <returns></returns>
        public static CaexObjectComparerResult CaexEquals(this CAEXObject caexBasicObj, CAEXObject compareWith, bool UseDictionary)
        {
            Tuple<CAEXBasicObject,CAEXBasicObject> tuple=null;

            if (UseDictionary)
                tuple = caexBasicObj.BeginCompare(compareWith);

            if (!caexBasicObj.CAEXPath().Equals(compareWith.CAEXPath()))
            {
                if (UseDictionary)
                {
                    CaexComparedObjects[tuple] |= CaexObjectComparerResult.PathDiff;
                    return CaexComparedObjects[tuple];
                }
                else
                    return CaexObjectComparerResult.PathDiff;
            }

            if (UseDictionary)
                return CaexComparedObjects[tuple];
            else
                return CaexObjectComparerResult.IsEqual;
        }
        
        /// <summary>
        /// Compare the CAEXPath(), the Attribute- and Interface-Definitions of two RoleClass Objects
        /// </summary>
        /// <param name="roleClass"></param>
        /// <param name="compareWith"></param>
        /// <returns></returns>
        public static CaexObjectComparerResult RoleClassEquals(this RoleFamilyType roleClass, RoleFamilyType compareWith, bool UseDictionary)
        {
            var tuple = roleClass.BeginCompare(compareWith);

            // Compare Caex Path
            if (roleClass.CaexEquals(compareWith, UseDictionary).HasFlag(CaexObjectComparerResult.PathDiff))
            {
                CaexComparedObjects[tuple] |= CaexObjectComparerResult.RoleClassDiff;
            }

            // Compare Attributes
            var attributeCompareResult = roleClass.AttributesEquals(compareWith, UseDictionary);
            if (attributeCompareResult.HasFlag (CaexObjectComparerResult.AttributeDiff) || 
                attributeCompareResult.HasFlag (CaexObjectComparerResult.AttributeCountDiff))
            {
                CaexComparedObjects[tuple] |= CaexObjectComparerResult.RoleClassDiff;
            }
            
            // Compare Interface Count
            if (roleClass.ExternalInterface.Exists != compareWith.ExternalInterface.Exists)
            {
                CaexComparedObjects[tuple] |= CaexObjectComparerResult.InterfaceCountDiff;
                CaexComparedObjects[tuple] |= CaexObjectComparerResult.RoleClassDiff;
            }
            else if (roleClass.ExternalInterface.Exists)
            {
                // Compare Interface Count
                if (roleClass.ExternalInterface.Count != compareWith.ExternalInterface.Count)
                {
                    CaexComparedObjects[tuple] |= CaexObjectComparerResult.InterfaceCountDiff;
                    CaexComparedObjects[tuple] |= CaexObjectComparerResult.RoleClassDiff;
                }

                // Compare Interface Definition
                foreach (InterfaceClassType externalInterface in roleClass.ExternalInterface)
                {
                    var comparableInterface = roleClass.ExternalInterface.FindCaexObjectByName(externalInterface.Name()) as InterfaceClassType;
                    if (comparableInterface != null)
                    {
                        if (externalInterface.InterfaceEquals(comparableInterface, UseDictionary).HasFlag(CaexObjectComparerResult.InterfaceClassDiff))
                        {
                            CaexComparedObjects[tuple] |= CaexObjectComparerResult.RoleClassDiff;
                        }
                    }
                }
            }          

            return CaexComparedObjects[tuple];
        }

        /// <summary>
        /// Comparing Interfaces (Attributes and CAEXPath)
        /// </summary>
        /// <param name="interfaceClass"></param>
        /// <param name="compareWith"></param>
        /// <returns></returns>
        public static CaexObjectComparerResult InterfaceEquals(this InterfaceClassType interfaceClass, InterfaceClassType compareWith, bool UseDictionary)
        {
            var tuple = interfaceClass.BeginCompare(compareWith);

            // Compare CaexPath
            if (interfaceClass.CaexEquals(compareWith, UseDictionary).HasFlag(CaexObjectComparerResult.PathDiff))
            {
                CaexComparedObjects[tuple] |= CaexObjectComparerResult.InterfaceClassDiff;
            }

            // Compare Attributes 
            if (interfaceClass.AttributesEquals(compareWith, UseDictionary).HasFlag(CaexObjectComparerResult.AttributeDiff))
            {
                CaexComparedObjects[tuple] |= CaexObjectComparerResult.InterfaceClassDiff;
            }
          
            return CaexComparedObjects[tuple];
        }


        /// <summary>
        /// Compare Attributes of two Objects with Attributes
        /// </summary>
        /// <param name="withAttribute"></param>
        /// <param name="compareWith"></param>
        /// <returns></returns>
        public static CaexObjectComparerResult AttributesEquals(this CAEXObject withAttribute, CAEXObject compareWith, bool UseDictionary)
        {
            var tuple = (withAttribute).BeginCompare(compareWith);

            if (withAttribute is IObjectWithAttributes && compareWith is IObjectWithAttributes)
            {
                var ObjectWithAttribute  = (IObjectWithAttributes)withAttribute;
                var compareWithAttribute = (IObjectWithAttributes)compareWith;

                //if (withAttribute.getAttributeField().Exists != compareWith.getAttributeField().Exists )
                //    return CaexObjectComparerResult.AttributeCountDiff;

                // Compare Attribute Count
                if (ObjectWithAttribute.getAttributeField().Count != compareWithAttribute.getAttributeField().Count)
                {
                    CaexComparedObjects[tuple] |= CaexObjectComparerResult.AttributeCountDiff;
                }

                // Compare Attribute Definition
                foreach (AttributeType attribute in ObjectWithAttribute.getAttributeField())
                {
                    var comparableAttribute = compareWithAttribute.GetAttribute(attribute.Name.Value);
                    if (comparableAttribute != null)
                    {
                        if (attribute.AttributeEquals(comparableAttribute, UseDictionary).HasFlag(CaexObjectComparerResult.AttributeDiff))
                        {
                            CaexComparedObjects[tuple] |= CaexObjectComparerResult.AttributeDiff;
                        }
                    }
                }
            }

            return CaexComparedObjects[tuple];
        }

        /// <summary>
        /// Compare two Attribute Types
        /// </summary>
        /// <param name="attribute"></param>
        /// <param name="compareWith"></param>
        /// <returns></returns>
        public static CaexObjectComparerResult AttributeEquals(this AttributeType attribute, AttributeType compareWith, bool UseDictionary)
        {
            var tuple = attribute.BeginCompare(compareWith);
           
            // Compare Name
            if ( attribute.CaexEquals(compareWith, UseDictionary).HasFlag (CaexObjectComparerResult.PathDiff))
                CaexComparedObjects[tuple] |= CaexObjectComparerResult.AttributeDiff;

            // Compare Attribute DataType
            if ( (attribute.AttributeDataType.Exists() != compareWith.AttributeDataType.Exists()) ||
                 (attribute.AttributeDataType.Exists() &&
                    (attribute.AttributeDataType.Value != compareWith.AttributeDataType.Value)))
            {
                CaexComparedObjects[tuple] |= CaexObjectComparerResult.AttributeDataTypeDiff;
                CaexComparedObjects[tuple] |= CaexObjectComparerResult.AttributeDiff;
            }
                
            // Compare Attribute Unit
            if ( (attribute.Unit.Exists() != compareWith.Unit.Exists()) ||
                 (attribute.Unit.Exists() && (attribute.Unit.Value != compareWith.Unit.Value) ) )
            {
                CaexComparedObjects[tuple] |= CaexObjectComparerResult.AttributeUnitDiff; 
                CaexComparedObjects[tuple] |= CaexObjectComparerResult.AttributeDiff;
            }

            // Compare nested Attributes
            attribute.AttributesEquals(compareWith, UseDictionary);

            return CaexComparedObjects[tuple];
        }
    }
}
